package ch.usi.inf.pf2;

import java.awt.Color;

import ch.usi.inf.pf2.cell.Cell;
import ch.usi.inf.pf2.cell.Converter;
import ch.usi.inf.pf2.cell.Data;

/**
 * @author Ebrisa Savina Mastrodicasa, Thomas Mantegazzi,Yotam Sharon
 */


/**
 * Sheet class defines the entire grid of cells as a
 * matrix of Cell objects.
 */
public class Sheet {
	
	private Cell[][] grid;
	private String name;
	private Converter converter;
	private Listener changes;
	private boolean hasBeenSaved;
	
	/**
	 * Construct a default 100columns X 100 rows Sheet 
	 * object.
	 */
	public Sheet()
	{
		name = "Document1";
		grid = new Cell[100][100];
		fillGrid();
		converter = new Converter();
		changes = new Listener(this);
		hasBeenSaved = false;
		//fillGridWithFormulas();
	}
	
	
	/**
	 * Construct a Sheet object.
	 * @param columns The desired number of columns in the sheet.
	 * @param rows The desired number of rows in the sheet.
	 * @param name The wanted name for the sheet.
	 */
	public Sheet(int columns, int rows, String name)
	{
		this.name = name;
		grid = new Cell[columns][rows];
		fillGrid();
		converter = new Converter();
		changes = new Listener(this);
		hasBeenSaved = false;
	}
	
	
	/**
	 * Fill the whole grid with formulas.
	 * Each formula depends on the value of the cell before.
	 * Needed to test performance
	 */
	public void fillGridWithFormulas() {
		int[] previousCell = null;
		for (int i = 1; i < grid.length; i++) {
			for (int j = 1; j < grid[0].length; j++) {
				if (i == 1 && j == 1) {
					previousCell = new int[]{i,j};
				} else if (previousCell == null || i > previousCell[0]) {
					converter.setCellReferenceNumbers(previousCell);
					converter.convertToString();
					String previousCellString = converter.getCellReference();
					insertDataAtCellForInit("= 2 + " + previousCellString, i, j);
					previousCell = new int[]{i,j};
				} else {
					converter.setCellReferenceNumbers(new int[]{i,j-1});
					converter.convertToString();
					insertDataAtCellForInit("= 2 + "+converter.getCellReference(), i, j);
					previousCell = new int[]{i,j};
				}
			}
		}
	}
	

	/**
	 * This method fill the grid with cell objects with a empty string as a content.
	 */
	private void fillGrid() {
		for(int i = 0; i< grid.length;i++){
			for(int j = 0; j < grid[i].length;j++){
				grid[i][j] = new Cell();
				grid[i][j].setContent("");
			}
		}
	}
		
	
	/**
	 * @return The number of rows of the sheet.
	 */
	public int getRows()
	{
		return grid[0].length;
	}
	
	
	/**
	 * @return The number of columns of the sheet.
	 */
	public int getColumns()
	{
		return grid.length;
	}
	
	
	/**
	 * @return The name of the sheet.
	 */
	public String getName()
	{
		return name;
	}
	
	/**
	 * Get the content of a Cell
	 * @param cellReference The position from which to extract the Cell content
	 * @return The content of the Cell
	 */
	public Data getCellByName(String cellReference) {
		converter.setCellReference(cellReference);
		converter.convertToNumbers();
		int[] position = converter.getCellReferenceNumbers();
		return getCell(position[0], position[1]).getContent();
	}
	
	/**
	 * Get the Converter
	 * @return the converter
	 */
	public Converter getConverter() 
	{
		return converter;
	}
	
	/**
	 * Set the name of the sheet.
	 * @param name The name wanted for the Sheet object.
	 */
	public void setName(String name)
	{
		this.name = name;
	}
	
	
	
	/**
	 * @return cell at the specified index.
	 * @param column The column number in the grid.
	 * @param row The row number in the grid. 
	 */
	public Cell getCell(int column, int row)
	{
		return grid[row][column];
	}
	
	
	/**
	 * @return The array of cells forming the grid of the sheet.
	 */
	public Cell[][] getGrid()
	{
		return grid;
	}
	
	/**
	 * @return True if the sheet has been saved.
	 */
	public boolean getHasBeenSaved()
	{
		return hasBeenSaved;
	}
	
	/**
	 * Set if the sheet has been saved.
	 * @param hasBeenSaved
	 */
	public void setHasBeenSaved(boolean hasBeenSaved)
	{
		this.hasBeenSaved = hasBeenSaved;
	}
	
	
	/**
	 * Initialize the sheet with the first row reserved to the
	 * literal indexes, and the first columns reserved to the
	 * numerical indexes (both are green).
	 */
	public void initializeSheet()
	{
		grid[0][0].setContent("");
		grid[0][0].setColor(Color.GREEN);
		for(int i = 1; i < getColumns(); i++)
		{
			Converter c = new Converter();
			c.setCellReferenceNumbers(new int[] {i,1});
			c.convertToString();
			grid[0][i].setContent(c.getCellLetters());
			grid[0][i].setColor(Color.GREEN);
		}
		for(int j = 1; j < getRows(); j++)
		{
			grid[j][0].setContent(" " + j);
			grid[j][0].setColor(Color.GREEN);
		}
	}
	
	
	/**
	 * Insert a given data into a specified cell in the grid.
	 * @param data The data to be inserted in the cell.
	 * @param letterNumber The number referring to the literal index of the cell.
	 * @param numberNumber The numerical index of the cell.
	 */
	public void insertDataAtCell(String data, int letterNumber, int numberNumber)
	{
		if(letterNumber == 0 || numberNumber == 0) {
			
		} else {
			grid[numberNumber][letterNumber].setContent(Data.createData(data));
			converter.setCellReferenceNumbers(new int[]{letterNumber, numberNumber});
			converter.convertToString();
			changes.addChangedCell(getCell(letterNumber, numberNumber), PossibleChanges.DATA, converter.getCellReference());
			changes.evaluateFirst();
		}
	}
	
	// fill the grid but without updating formulas..used for initializing sheet
	public void insertDataAtCellForInit(String data, int letterNumber, int numberNumber)
	{
		if(letterNumber == 0 || numberNumber == 0) {
			
		} else {
			grid[numberNumber][letterNumber].setContent(Data.createData(data));
			converter.setCellReferenceNumbers(new int[]{letterNumber, numberNumber});
			converter.convertToString();
			changes.addChangedCell(getCell(letterNumber, numberNumber), PossibleChanges.DATA, converter.getCellReference());
		}
	}
	
	
	/**
	 * Insert the given data i a specified cell Reference in the grid
	 * @param data The data to insert (String)
	 * @param cellReference The string of the cell reference like "A1"
	 */
	public void insertDataAtCell(String data, String cellReference) {
		converter.setCellReference(cellReference);
		converter.convertToNumbers();
		int[] position = converter.getCellReferenceNumbers();
		int letterNumber = position[0];
		int numberNumber = position[1];
		grid[numberNumber][letterNumber].setContent(Data.createData(data));
		changes.addChangedCell(getCell(numberNumber, letterNumber), PossibleChanges.DATA, cellReference);
		changes.evaluateFirst();
		
	}
	
	
	/**
	 * @return only the grid without the first row and the
	 * first column
	 */
	public Cell[][] getGridWithoutHeaders() {
		Cell[][] data = new Cell[getGrid().length -1][getGrid().length - 1];
		for(int i = 0; i<data.length; i++) {
			for(int j = 0; j < data.length; j++) {
				data[i][j] = getGrid()[i+1][j+1];
			}
 		}
		return data;
	}
	
	public void setGrid (Cell[][] newgrid)
	{
	    grid=newgrid;
	}
	

	/**
	 * Insert a row at a desired index.
	 * @param index The place where the row must be inserted.
	 */
	public void insertRowAt (int index)
	{
	    index++;
	    Data[] old = new Data[grid[0].length];
	    for(int i=0; i<grid.length; i++)
	    {  
		   for(int j=0; j<grid[i].length; j++)
		   {
			  if(i==index)
			  {
				 old[j]=grid[i][j].getContent();
				 grid[i][j].setContent("");
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
			  if(i>index)
			  {
				 Data tmp= old[j];
				 old[j]=grid[i][j].getContent();
				 grid[i][j].setContent(tmp);
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
			  if(j==grid[i].length+1)
				 i++;	  
		   }
	    }
	}
	
	public void deleteRowAt (int index)
	{
	    index++;
	    Data[] newC = new Data[grid[0].length];
	    for(int i=0; i<grid.length; i++)
	    {  
		   for(int j=0; j<grid[i].length; j++)
		   {
			  if(i==index)
			  {
				 newC[j]=grid[i+1][j].getContent();
				 grid[i][j].setContent(newC[j]);
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
			  if(i>index && i+1<grid.length)
			  {
				 newC[j]=grid[i+1][j].getContent();
				 grid[i][j].setContent(newC[j]);
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
		   }
	    } 
	}
	
	
	/**
	 * Insert a column at a desired index.
	 * @param index The place where the column must be inserted.
	 * @return The sheet comprising the column just inserted.
	 */
	public void insertColumnAt (int index)
	{
	    index++;
	    Data[] old = new Data[grid.length];
	    
	    for(int i=0; i<grid.length; i++)
	    {
		   for(int j=0; j<grid[0].length; j++)
		   {
			  if(j==index)
			  {
				 old[i]=grid[i][j].getContent();
				 grid[i][j].setContent("");
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
			  if(j>index)
			  {
				 Data tmp=old[i];
				 old[i]=grid[i][j].getContent();
				 grid[i][j].setContent(tmp);
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
		   }
	    }
	}


    public void deleteColumnAt(int index)
    {
	   index++;
	    Data[] newC = new Data[grid.length];
	    
	    for(int i=0; i<grid.length; i++)
	    {
		   for(int j=0; j<grid[0].length; j++)
		   {
			  if(j==index)
			  {
				 newC[i]=grid[i][j+1].getContent();
				 grid[i][j].setContent(newC[i]);
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
			  if(j>index && j+1<grid[0].length)
			  {
				 newC[i]=grid[i][j+1].getContent();
				 grid[i][j].setContent(newC[i]);
				 converter.setCellReferenceNumbers(new int[]{j, i});
				 converter.convertToString();
				 changes.addChangedCell(grid[i][j], PossibleChanges.DATA, converter.getCellReference());
			  }
		   }
	    }
    }
}
